//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.IO;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats.Biff.Tokens
{   
    
    internal class ArrayToken : ComplexFormulaToken
    {
        private readonly AccessorType _type;

        public ArrayToken(AccessorType type)
        {
            _type = type;
        }
        public ArrayToken (AccessorType type, IEnumerable<IEnumerable<object>> values): this(type)
        {
            _values = Enumerable.ToList(Enumerable.Select(values, v => Enumerable.ToList(v)));
        }
        public override FormulaTokenId TokenId
        {
            get {
                switch (_type)
                {
                        case AccessorType.Array:
                        return FormulaTokenId.ArrayA;
                        case AccessorType.Reference:
                        return FormulaTokenId.Array;
                        case AccessorType.Value:
                        return FormulaTokenId.ArrayV;
                    default:
                        throw new NotSupportedException();

                }}
        }
        enum ArrayValueType : byte
        {
            Nil = 0,
            Double = 1,
            String = 2,
            Boolean = 4,
            Error = 0x10,
        }
        public override void ReadAfter(BinaryReader reader)
        {
            var columnCount = reader.ReadByte() + 1;
            var rowCount = reader.ReadUInt16() + 1;
            _values = new List<List<object>>();
            for (int row = 0; row < rowCount; row++)
            {
                var currentRow = new List<object>();
                _values.Add(currentRow);
                for (int column = 0; column < columnCount; column++)
                {
                    currentRow.Add(ReadValue(reader));
                }
            }
        }

        public override void WriteAfter(BinaryWriter writer)
        {
            var columnCount = _values.Count;
            int rowCount = 1;
            if (columnCount > 0)
                rowCount = _values[0].Count;
            writer.Write((byte)(columnCount - 1));
            writer.Write((ushort)(rowCount - 1));
            foreach (var r in _values)
            {
                foreach (var c in r)
                {
                    writeValue(c, writer);
                }
            }
        }

        public override void WriteContent(FormulaWriteContext context)
        {
            context.Writer.Write(data);
        }

        private static void writeValue(object value, BinaryWriter writer)
        {
            if (value == null)
            {
                writer.Write((byte)ArrayValueType.Nil);
                writer.Write((double)0);
                return;
            }
            if (value is double)
            {
                writer.Write((byte)ArrayValueType.Double);
                writer.Write((double) value);
                return;
            }
            if (value is string)
            {
                writer.Write((byte)ArrayValueType.String);
                string s1 = (string) value;
                XLUnicodeString s2 = (XLUnicodeString) s1;
                s2.Write(writer);
                return;
            }
            if (value is bool)
            {
                writer.Write((byte)ArrayValueType.Boolean);
                if ((bool)value)
                    writer.Write((ulong)1);
                else
                {
                    writer.Write((ulong)0);
                }
                return;
            }
            if (value is Error)
            {
                writer.Write((byte)ArrayValueType.Error);
                ulong ul = (ulong) ((Error) value).Type;
                writer.Write(ul);
                return;
            }
            throw new NotImplementedException(value.ToString());
        }

        private List<List<object>> _values;
        private byte[] data = new byte[7];

        public override ushort GetLength(FormulaReadContextType formulaReadContextType)
        {
            return 7;
        }

        internal override void ReadContent(FormulaReadContext context)
        {
            this.data =  context.ReadBytes(7);
        }

        private static object ReadValue(BinaryReader reader)
        {
            var t = (ArrayValueType) reader.ReadByte();
            switch (t)
            {
                    case ArrayValueType.Nil:
                    reader.ReadDouble();
                    return null;
                case ArrayValueType.Double:
                    return reader.ReadDouble();
                case ArrayValueType.String:
                    XLUnicodeString s = new XLUnicodeString();
                    s.Read(reader);
                    return (string) s;
                    //\\return StringHelper.ReadUtf8String(reader, reader.ReadUInt16());
                case ArrayValueType.Boolean:
                    return reader.ReadUInt64() != 0;
                    case ArrayValueType.Error:
                    ulong l = reader.ReadUInt64();
                    int i = (int) l;
                    i = i & 0xFF;
                    return Errors.GetError((ErrorType) i);
                default:
                    throw new NotSupportedException();
            }
        }

        internal override Expression GetExpression(FormulaEvaluationContext context)
        {
            return new 
            ArrayExpression(getValueExpressions());

        }

        private IEnumerable<IEnumerable<Expression>> getValueExpressions()
        {
            foreach (var list in _values)
            {
                yield return getRowValueExpressions(list);
            }
        }

        private static IEnumerable<Expression> getRowValueExpressions(List<object> objects)
        {
            foreach (var o in objects)
            {
                if (o == null)
                    yield return new MissingExpression();
                if (o is double)
                    yield return new DoubleConstantExpression(o.ToString(), (double)o);
                else if (o is string)
                    yield return new StringConstantExpression(o.ToString());
                else if (o is bool)
                    yield return new BooleanConstantExpression((bool)o);
                else if (o is Error)
                    yield return new ErrorExpression((Error) o);
                else
                {
                    throw new NotSupportedException(o.ToString());
                }
            }
        }
    }
}